import os

from PyQt4 import QtCore

from limber import componentfinder
from limber import componentchooser

from limber.documents import basetextdocument
from limber.views import basetextview
from limber.viewdisplayers import tabbedviewdisplayer


class ComponentManagerError(Exception):
    '''Exception raised by the `ComponentManager` class.'''
    
    pass


class ComponentManager(QtCore.QObject):
    '''Manage the selection of components.

    This class is responsible for finding, loading and choosing
    the components that can be used.

    In fact it delegates most of the tasks to other classes, such
    as *ComponentChooser* and *ComponentFinder*.
    '''

    component_registered = QtCore.pyqtSignal(name='componentRegistered')
    component_unregistered = QtCore.pyqtSignal(name='componentUnregistered')

    def __init__(self, parent):
        super(ComponentManager, self).__init__(parent)

        self._userdir = parent.userdir_path()
        self._systemdir = parent.systemdir_path()

        self._finder = componentfinder.ComponentFinder(self._userdir,
                                                       self._systemdir)
        self._chooser = componentchooser.ComponentChooser()

        self._components = {}
        self._components_infos = {}

    @property
    def chooser(self):
        '''The component chooser of the manager.'''

        return self._chooser

    @property
    def finder(self):
        '''The component finder of the manager.'''

        return self._finder

    def components_names(self):
        '''Generator that yields the names of the components.'''

        for name in self._components:
            yield name

    def number_of_components(self):
        '''Return the number of registered components.'''

        return len(self._components)

    def has_component(self, name):
        '''Return True if there is a component called *name*.'''

        return name in self._components

    def component(self, name):
        '''Return the component associated with the given *name*.'''
        
        try:
            return self._components[name]
        except KeyError:
            raise ComponentManagerError('There is no %r component registered.' % name)

    def load_components(self):
        '''Loads the components from the directories.'''

        comp_userdir = os.path.join(self._userdir, 'Components')
        comp_sysdir = os.path.join(self._systemdir, 'Components')
        dirs = (comp_userdir, comp_sysdir)
        for dirname in dirs:
            infos = self._finder.find_components_infos(dirname)
            for info in infos:
                component = self._finder.load_component(info)
                self._register_component(component, info)
    
    def _get_component_with_interface(self, name, interface):
        '''Return the component *name* with the given interface.
        
        It raises ComponentManagerError if the component does
        not exist or if the component does not implement
        the given interface.
        '''
        
        component = self.component(name)
        
        infos = self._components_infos[name]
        if infos.interface != interface:
            raise ComponentManagerError('The component %r implements the %r interface instead '
                                        'of the %r interface.' % (name,  infos.interface, interface))
        
        return component
    
    def get_document(self, name):
        '''Return the document component with the given name.
        
        It raises a `ComponentManagerError` if the component
        is not present or if the component is not a document.
        '''
        
        return self._get_component_with_interface(name, 'document')
    
    def get_view(self, name):
        '''Return the view component with the given name.
        
        It raises a `ComponentManagerError` if the component
        is not present or if the component is not a view.
        '''
        
        return self._get_component_with_interface(name, 'view')
    
    def get_viewdisplayer(self, name):
        '''Return the viewdisplayer component with the given name.
        
        It raises `ComponentManagerError` if the component
        is not present or if the component is not a viewdisplayer.
        '''
        
        return self._get_component_with_interface(name, 'viewdisplayer')
    
    def _register_component(self, component, infos, overwrite=False):
        '''Given a component and its infos, registers it.'''
        
        abils = self._finder.get_abilities(infos.abilities)
        
        if (infos.name in self._components or
            infos.name in self._components_infos):
            if not overwrite:
                raise ComponentManagerError('The component %r is already '
                                            'registered.' % infos.name)
        
        self._components[infos.name] = component
        self._components_infos[infos.name] = infos
        self._chooser.register_component(component, abils)
        self.component_registered.emit()
    
    def register_component(self, component, infos=None, overwrite=False):
        '''Registers the given component.'''
        
        if infos is None:
            try:
                infos = component.infos
            except AttributeError:
                raise ComponentManagerError('No informations supplied.')
        
        self._register_component(component, infos, overwrite)
    
    def register_components(self, components, infos=(), overwrite=False):
        '''Register an iterable of components.'''
        
        for pos, component in enumerate(components):
            try:
                comp_infos = infos[pos]
            except IndexError:
                comp_infos = None
            self.register_component(component, comp_infos, overwrite)
    
    def load_builtin_components(self):
        '''Loads built-in components from the limber package.'''
        
        builtins = (
            (basetextdocument.BaseTextDocument, basetextdocument.INFOS),
            (basetextview.BaseTextView, basetextview.INFOS),
            (tabbedviewdisplayer.TabbedViewDisplayer, tabbedviewdisplayer.INFOS),
            )
        
        for component, infos in builtins:
            self._register_component(component, infos)
    
    def components_names_by_interface(self, interface):
        '''Yields all the names of avaiable components with the given *interface*.
        
        The *interface* argument must be one of the following strings:
        
            * document
            * viewdisplayer
            * view
        
        Otherwise a `ValueError` is raised.
        '''
        
        if interface not in ('view', 'viewdisplayer', 'document'):
            raise ValueError('Invalid interface name.')
        
        for name, infos in self._components_infos.iteritems():
            if infos.interface == interface:
                yield name
